%% -*- coding: utf-8 -*-

%% @doc a scanner for templates
%%
-module(templ_scan).
-export([string/1,string/2,tokens/3,scan/7,format_error/1]).

-import(lists, [reverse/1,last/1]).

-include_lib("../include/templ_scan.hrl").


%% @spec string([char()], integer()) -> scan_results()
%%
string(Cs, Pos) when is_list(Cs), is_integer(Pos) ->
    scan(Cs, [], [], [Pos], [], [], ?LDelim).


%% scan loop

%% TODO
%% string
done(eof, [], Toks, Pos, eos) ->
    {ok,reverse(Toks),?GetPos(Pos)};
done(eof, Errors, _Toks, Pos, eos) ->
    {Error,ErrorPos} = lists:last(Errors),
    {error,{ErrorPos,?MODULE,Error},?GetPos(Pos)};
done(Cs, Errors, Toks, Pos, eos) ->
    scan(Cs, [], Toks, Pos, eos, Errors, ?LDelim);
% %% Debug clause for chopping string into 1-char segments
% done(Cs, Errors, Toks, Pos, [H|T]) ->
%    scan(Cs++[H], [], Toks, Pos, T, Errors);
done(Cs, Errors, Toks, Pos, []) ->
    scan(Cs++eof, [], Toks, Pos, eos, Errors, ?LDelim);
%% Stream
done(Cs, [], [{dot,_}|_]=Toks, Pos, io) ->
    {done,{ok,reverse(Toks),Pos},Cs};
done(Cs, [], [_|_], Pos, io) ->
    {done,{error,{Pos,?MODULE,scan},Pos},Cs};
done(Cs, [], [], Pos, eof) ->
    {done,{eof,Pos},Cs};
done(Cs, [], [{dot,_}|_]=Toks, Pos, eof) ->
    {done,{ok,reverse(Toks),Pos},Cs};
done(Cs, [], _Toks, Pos, eof) ->
    {done,{error,{Pos,?MODULE,scan},Pos},Cs};
done(Cs, Errors, _Toks, Pos, io) ->
    {Error,ErrorPos} = lists:last(Errors),
    {done,{error,{ErrorPos,?MODULE,Error},Pos},Cs};
done(Cs, Errors, _Toks, Pos, eof) ->
    {Error,ErrorPos} = lists:last(Errors),
    {done,{error,{ErrorPos,?MODULE,Error},Pos},Cs}.


%% @spec scan(stream(), stack(), [tok()], pos(), state(), errors(), string()) 
%%         -> result()
%% @doc frontend of scan. scan for a text
%%
scan([C|Cs], Stack, Toks, PStack, State, Errors, [DC|DCs]) 
  when C =:= DC ->
	scan_delim(Cs, [C,Stack], Toks, PStack, State, Errors, DCs);
scan([C|Cs], Stack, Toks, PStack, State, Errors, Delim) ->
    scan(Cs, [C|Stack], Toks, PStack, State, Errors, Delim);
scan([], Stack, Toks, PStack, State, Errors, Delim) ->
    more([], Stack, Toks, PStack, State, Errors, Delim, fun scan/7);
scan(Eof, Stack, Toks, PStack, State, Errors, _Delim) ->
    Pos = reverse(PStack),
    Text = reverse(Stack),
    done(Eof, Errors, [{text,Pos,Text}|Toks], PStack, State).


%% @doc scan for a delimiter
%%
scan_delim([C|Cs], Stack, Toks, PStack, State, Errors, [DC|DCs]) 
  when C =:= DC ->
	scan_delim(Cs, [C|Stack], Toks, PStack, State, Errors, DCs);
scan_delim([], Stack, Toks, PStack, State, Errors, Delim) ->
    more([], Stack, Toks, PStack, State, Errors, Delim, fun scan_delim/7);
scan_delim(Cs, Stack, Toks, PStack, State, Errors, []) ->
    Pos = reverse(PStack),
    TStack = last(Stack),
	Text = reverse(TStack),
	templel_scan:scan(Cs, [], [{l_delim,Pos},{text,Pos,Text}|Toks], 
					  ?PushPos(PStack, 1), State, Errors, ?RDelim);
scan_delim(Cs, Stack, Toks, PStack, State, Errors, _Delim) ->
    [TStack|Buf] = reverse(Stack),
	DStack = reverse(Buf),
	scan(Cs, DStack++TStack, Toks, PStack, State, Errors, ?LDelim).
